classdef (CaseInsensitiveProperties = true) heuristics
    properties
        states=[];
		moves=[];
		goal=[];
		solved=[];
		constants=[];
		threshold = 3
    end
    methods
        function self = heuristics(states,moves,goal,constants)
            self.states=states;
            self.moves=moves;
            self.goal=goal;
            self.solved=solver(states,moves,goal);
            self.constants=constants;
        end
        
        function x = correctValue(self,nodes,parents) % El valor correcto del nodo de acuerdo a Q-learning
            if isa(nodes,'node')
                flag = 1;
            elseif isa(nodes,'cell') && isa(nodes{1},'node')
                flag = 2;
            else
                error('Wrong data type supplied to heuristic');
            end
            d = self.solved.distance;
            if flag == 1
                dist = d(nodes.representation);
            elseif flag == 2
                dist = [];
                for n_ind = 1:length(nodes)
                    n=nodes{n_ind};
                    dist = [dist,d(n.representation)]; %#ok<AGROW>
                end
            end
            x = repmat(self.constants.gamma,size(dist)).^(dist);
        end
        
        function x = thresholdValue(self,nodes,parents)  % Estima bien hasta un umbral
            if isa(nodes,'node')
                flag = 1;
            elseif isa(nodes,'cell') && isa(nodes{1},'node')
                flag = 2;
            else
                error('Wrong data type supplied to heuristic');
            end
            d = self.solved.distance;
            if flag == 1
                dist = d(nodes.representation);
            elseif flag == 2
                dist = [];
                for n_ind = 1:length(nodes)
                    n=nodes{n_ind};
                    dist = [dist,d(n.representation)]; %#ok<AGROW>
                end
            end
            dist(dist>self.threshold)=self.threshold;
            x = repmat(self.constants.gamma,size(dist)).^(dist);
        end
        
        function x = neverLookBack(self,nodes,parents) % Penaliza volver hacia el padre pero le da lo mismo lo demas
            if isa(nodes,'cell') && isa(nodes{1},'node')
                nodes = [nodes{:}];
            elseif ~isa(nodes,'node')
                error('Wrong data type supplied to heuristic');
            end
            if isa(parents,'cell') && isa(parents{1},'node')
                parents = [parents{:}];
            elseif ~isa(parents,'node')
                error('Wrong data type supplied to heuristic');
            end
            reps = [nodes.representation];
            gramps = [parents.parent_rep];
            x = zeros(size(reps));
            x((reps-gramps)==0) = -1; % Vuelven para atras
        end
        
        function x = keepOnSpinning(self,nodes,parents) % Favorece las acciones que mantienen el sentido de giro
            if isa(nodes,'cell') && isa(nodes{1},'node')
                nodes = [nodes{:}];
            elseif ~isa(nodes,'node')
                error('Wrong data type supplied to heuristic');
            end
            if isa(parents,'cell') && isa(parents{1},'node')
                parents = [parents{:}];
            elseif ~isa(parents,'node')
                error('Wrong data type supplied to heuristic');
            end
            reps = [nodes.representation]; reps = reps(:);
            pars = [parents.representation]; pars = pars(:);
            gramps = [parents.parent_rep]; gramps = gramps(:);
            reps = [floor(reps/1000), floor(reps/100)-floor(reps/1000)*10, mod(reps,10), (mod(reps,100)-mod(reps,10))/10]; % Convierte a una representacion donde rotar horaria o antihorariamente
            pars = [floor(pars/1000), floor(pars/100)-floor(pars/1000)*10, mod(pars,10), (mod(pars,100)-mod(pars,10))/10]; % corresponde a mover hacia las fichas hacia la derecha o la izquierda respectivamente
            gramps = [floor(gramps/1000), floor(gramps/100)-floor(gramps/1000)*10, mod(gramps,10), (mod(gramps,100)-mod(gramps,10))/10]; % o a mover el 0 hacia la izquierda o la derecha respectivamente
            x = zeros(size(reps,1),1);
            [nada,libres1] = find(gramps==0);
            [nada,libres2] = find(pars==0);
            [nada,libres3] = find(reps==0);
            libres1 = libres1-1; libres2 = libres2-1; libres2 = libres3-1;
            for i = 1:length(libres1)
                if libres1(i)==mod(libres2(i)+1,4) % movimiento horario
                    if libres2(i)==mod(libres3(i)+1,4)
                        x(i) = 1;
                    end
                elseif libres1(i)==mod(libres2(i)-1,4)
                    if libres2(i)==mode(libres3(i)-1,4)
                        x(i) = 1;
                    end
                end
            end
        end
        
        function x = greedyPick(self,nodes,parents) % Favorece ubicar a una ficha en su casa.
            if isa(nodes,'cell') && isa(nodes{1},'node')
                nodes = [nodes{:}];
            elseif ~isa(nodes,'node')
                error('Wrong data type supplied to heuristic');
            end
            reps = [nodes.representation]; reps = reps(:);
            g = repmat(self.solved.goal,size(reps,1),1);
            reps = [floor(reps/1000), floor(reps/100)-floor(reps/1000)*10,(mod(reps,100)-mod(reps,10))/10,mod(reps,10)];
            g = [floor(g/1000), floor(g/100)-floor(g/1000)*10,(mod(g,100)-mod(g,10))/10,mod(g,10)];
            dist = reps-g;
            dist = sum(dist~=0,2);
            x = repmat(self.constants.gamma,size(dist)).^(dist);
        end
        
        function funchandle = returnLambda(self,varargin)
            % varargin must be a method name followed by its value. The
            % values should be positive
            if isempty(varargin)
                error('Unspecified method for the returnLambda function');
            elseif mod(length(varargin),2)==1
                error('Uneven number of inputs for returnLambda.\nThe inputs must be [methodName,methodVal]');
            else
                methodList = methods(self);
                methodList(strcmp(methodList,'heuristics')==1| strcmp(methodList,'returnLambda')==1)=[];
                methodList = methodList(:);
                heuristicList = cell(length(varargin)/2,1);
                params = zeros(size(heuristicList));
                j=0;
                for i=1:length(varargin)/2
                    ind = find(strcmpi(varargin{2*i-1},methodList)==1,1);
                    if ~isempty(ind) && isnumeric(varargin{2*i})
                        j = j+1;
                        params(j) = varargin{2*i};
                        heuristicList{j} = methodList{ind};
                    elseif ~isnumeric(varargin{2*i})
                        error('Non-numeric methodVal');
                    end
                end
            end
            inds = params==0 | isnan(params);
            if sum(inds)>0
                heuristicList = heuristicList(~inds);
                params = params(~inds);
            end
            funchandle = @(n,p) sum(cellfun(@(y) eval(['self.',y,'(n,p)']),heuristicList).*params);
        end
    end
end